/***************************************************************************
 *   Copyright (C) 2008 by Eduardo Gurgel Pinho                            *
 *   edgurgel@gmail.com                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
***************************************************************************/

#include "local_illumination.h"

#include "common/scene.h"
#include "common/random_generator.h"
#include "light/light.h"
#include "material/light_material.h"
#include "material/object_material.h"
#include "material/color_material.h"
#include "material/texture_material.h"
#include "object/object.h"
#include <cmath>

namespace Sirat {

namespace Ray_Tracing {

Common::Random_Generator randomnum(-1,1);
//Rand random(-1,1);
	
Common::Color Local_Illumination::calculate_color(Object::Object* _object,const Math::Ray& _ray,const Math::Vector& _point, Common::Scene* _scene,const unsigned int _shadow_rays) {
	const double attenuation= _scene->get_attenuation(_point.sub_vect(_ray.get_origin()).length());
	
	Common::Color cor;
	double diffuse = 0.0,specular = 0.0;
	
	/**
	 * Getting the object and the material
	 */
	Material::Object_Material* object_material = _object->get_material();
	Common::Color object_ambient, object_diffuse, object_specular;
	double object_brightness = 1.0;
	
	switch(object_material->get_type()) {
	
		case Material::Color_Material_Type: {
			/**
			 * Color Material 
			 */
			Material::Color_Material* color_material = static_cast<Material::Color_Material*>(object_material);
			object_ambient = color_material->get_ambient();
			object_diffuse = color_material->get_diffuse();
			object_specular = color_material->get_specular();
			object_brightness = color_material->get_brightness();
			break;
		}
		case Material::Texture_Material_Type: {
			/** 
			 * Texture Material 
			 */
			Material::Texture_Material* texture_material = static_cast<Material::Texture_Material*>(object_material);
			tuple<double,double> u_v = _object->get_u_v(_point);
			double u = u_v.get<0>();
			double v = u_v.get<1>();
			object_ambient = object_diffuse = object_specular = texture_material->get_texel(u,v);
			break;
		}
		default: break; 
	}
	
	/* Ambient*/
	double ambient = _scene->get_ambient(); 
	ambient = 0.0;
	Math::Vector normal = _object->get_normal(_point);
	
	for(unsigned int l = 0;l < _scene->get_lights_size();l++) { // For each light, calculate specular and diffuse
            
		Light::Light* light = _scene->get_light(l);


		/*Light attributes*/

		Material::Light_Material* light_material = light->get_material();
 		const Common::Color light_color = light_material->get_color();
		const double light_energy = light_material->get_energy();
		
		tuple<Math::Vector*,unsigned int> sample_points;
		/*
		 * shadow_rays = 0 means normal shadow testing.
		 */
		if(_shadow_rays > 0) {
			sample_points = light->get_sample_points(randomnum,_shadow_rays);
		} else sample_points = light->get_sample_points();
		
		Math::Vector* points = sample_points.get<0>();
		unsigned int n = sample_points.get<1>();
		
		for(unsigned int i = 0;i < n;i++) {
			
			Math::Ray shadow_testing(_point,points[i].sub_vect(_point));
		
			const double distance = shadow_testing.get_direction().length();
		
			/* Prevent auto-shadowing */
			shadow_testing.set_origin(shadow_testing.get_origin()+(shadow_testing.get_direction()*0.0001));
	
			shadow_testing.set_direction(shadow_testing.get_direction().normalize());
		
			if(!_scene->object_occlusion(shadow_testing,distance)) {
				const Math::Vector L = (points[i] - _point).normalize();
			
				/* Diffuse */
				diffuse += calculate_diffuse(L,normal);

				/*  Specular */
				specular += calculate_specular(L,normal,_ray,object_brightness);	
			}
			
			
		}
		
		if(points != 0) delete[] points;
		diffuse /= n;
		specular /= n;
		specular = 0;
		cor = cor + attenuation*(specular*light_color*object_specular + diffuse*light_color*object_diffuse + ambient*light_color*object_ambient);
	}
	return cor;
}

Math::Vector Local_Illumination::calculate_reflection(const Math::Vector& _incident,const Math::Vector& normal) {
	/* comment? :D */
	return  (normal.scalar_product(normal.dot_product(_incident)*2.0)).sub_vect(_incident);
}

Math::Vector Local_Illumination::light_vector(const Math::Vector& _light,const Math::Vector& _point) {
    return (_light - _point).normalize();
}

Math::Ray Local_Illumination::reflected_ray(const Math::Ray& _ray,const Math::Vector& _point,const Math::Vector& _normal) { 
	/* Prevent auto-reflection with a offset 0.001*/
	const Math::Vector point = _point+(_normal*0.001);
	
	const Math::Vector incident = _ray.get_direction().scalar_product(-1.0);
	const Math::Vector reflection = calculate_reflection(incident,_normal.normalize());
	return Math::Ray(point,reflection);
}

double Local_Illumination::calculate_diffuse(const Math::Vector& L, const Math::Vector& _normal) {
	return std::max(0.0,L.dot_product(_normal));	
}

double Local_Illumination::calculate_specular(const Math::Vector& L,const Math::Vector& _normal,const Math::Ray& _ray,const double brightness) {

	Math::Vector reflection = calculate_reflection(L,_normal);
	return std::pow(std::max(0.0,((_ray.get_direction().scalar_product(-1.0)).dot_product(reflection))),brightness);
	
}

}

}
